﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text.RegularExpressions;
using NLog;

namespace TfsGiggle.Core.Parsers
{
    /// <summary>
    /// Parser using CTags. Creates a small pool of ctags processes to process the parsing jobs.
    /// </summary>
    class CTagsParser : IParser, IDisposable
    {
        static LoggerWithEventId _logger = (LoggerWithEventId)LogManager.GetCurrentClassLogger(typeof(LoggerWithEventId));
        List<string> _usingDeclarations = new List<string>();
        List<string> _classDeclarations = new List<string>();
        List<string> _interfaceDeclarations = new List<string>();
        List<string> _methodDeclarations = new List<string>();
        static readonly Lazy<ConcurrentBag<CTagsProcessWrapper>> _poolOfCTagsProcesses = new Lazy<ConcurrentBag<CTagsProcessWrapper>>(() => CreateCTagsPool());
        static Regex _reUsings = new Regex(@"^(?<name>[\w]+)[\s]+using", RegexOptions.Multiline | RegexOptions.Compiled);
        static Regex _reClasses = new Regex(@"^(?<name>[\w]+)[\s]+class", RegexOptions.Multiline | RegexOptions.Compiled);
        static Regex _reInterfaces = new Regex(@"^(?<name>[\w]+)[\s]+interface", RegexOptions.Multiline | RegexOptions.Compiled);
        static Regex _reMethods = new Regex(@"^(?<name>[\w]+)[\s]+(method|function)", RegexOptions.Multiline | RegexOptions.Compiled);

        /// <summary>
        /// Put ctags processes into the bag and you'd better to put'em back when you are done
        /// </summary>
        /// <returns></returns>
        private static ConcurrentBag<CTagsProcessWrapper> CreateCTagsPool()
        {
            var bag = new ConcurrentBag<CTagsProcessWrapper>();

            // Add limited number of ctags processes; concurrent access is also limited to the TFS (the VersionControl API)
            bag.Add(new CTagsProcessWrapper(Settings.CTagsFilePath));
            bag.Add(new CTagsProcessWrapper(Settings.CTagsFilePath));
            bag.Add(new CTagsProcessWrapper(Settings.CTagsFilePath));
            bag.Add(new CTagsProcessWrapper(Settings.CTagsFilePath));
            return bag;
        }

        /// <summary>
        /// Parse the content in sourceCodeContent
        /// </summary>
        /// <param name="fileName"></param>
        /// <param name="sourceCodeContent"></param>
        public void Parse(string fileName, string sourceCodeContent)
        {
            _logger.Trace("enter '{0}'", fileName);
            CTagsProcessWrapper parser = null;
            string result;
            try
            {
                var timer = new Stopwatch();
                timer.Start();
                _poolOfCTagsProcesses.Value.TryTake(out parser);
                timer.Stop();
                _logger.Trace("Acquiring a ctags process took {0} ticks", timer.ElapsedTicks);
                timer.Restart();
                result = parser.Parse(fileName, sourceCodeContent);
                timer.Stop();
                _logger.Trace("Parsing '{0}' took {1} ticks", fileName, timer.ElapsedTicks);
            }
            finally
            {
                // Put me back if the ctags process is still running
                if (!parser.HasExited)
                {
                    _poolOfCTagsProcesses.Value.Add(parser);
                }
                else
                {
                    _logger.Trace("Creating a new ctags process to replace the departed");
                    _poolOfCTagsProcesses.Value.Add(new CTagsProcessWrapper(Settings.CTagsFilePath));
                }
            }
            string name;
            foreach (Match match in _reUsings.Matches(result))
            {
                name = match.Groups["name"].Value;
                if (!_usingDeclarations.Contains(name))
                {
                    _usingDeclarations.Add(name);
                }
            }

            foreach (Match match in _reClasses.Matches(result))
            {
                name = match.Groups["name"].Value;
                if (!_classDeclarations.Contains(name))
                {
                    _classDeclarations.Add(name);
                }
            }

            foreach (Match match in _reInterfaces.Matches(result))
            {
                name = match.Groups["name"].Value;
                if (!_interfaceDeclarations.Contains(name))
                {
                    _interfaceDeclarations.Add(name);
                }
            }

            foreach (Match match in _reMethods.Matches(result))
            {
                name = match.Groups["name"].Value;
                if (!_methodDeclarations.Contains(name))
                {
                    _methodDeclarations.Add(name);
                }
            }
            _logger.Trace("Found {0} usings, {1} classes, {2} interfaces, and {3} methods for '{4}'",
                _usingDeclarations.Count,
                _classDeclarations.Count,
                _interfaceDeclarations.Count,
                _methodDeclarations.Count,
                fileName);
            _logger.Trace("exit");
        }

        /// <summary>
        /// Name
        /// </summary>
        public string Name
        {
            get { return "CTagsParser"; }
        }

        /// <summary>
        /// Usings
        /// </summary>
        public List<string> UsingDeclarations
        {
            get { return _usingDeclarations; }
        }

        /// <summary>
        /// Classes
        /// </summary>
        public List<string> ClassDeclarations
        {
            get { return _classDeclarations; }
        }

        /// <summary>
        /// Interfaces
        /// </summary>
        public List<string> InterfaceDeclarations
        {
            get { return _interfaceDeclarations; }
        }

        /// <summary>
        /// Methods
        /// </summary>
        public List<string> MethodDeclarations
        {
            get { return _methodDeclarations; }
        }

        void Dispose(bool isDisposing)
        {
            if (isDisposing)
            {
            }
        }

        /// <summary>
        /// Dispose
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
    }
}